3a402c1d1d77a3a8b31c423aefdce635af647af7
[juce.git] /
1 /*\r
2   ==============================================================================\r
3 \r
4    This file is part of the JUCE library.\r
5    Copyright (c) 2017 - ROLI Ltd.\r
6 \r
7    JUCE is an open source library subject to commercial or open-source\r
8    licensing.\r
9 \r
10    The code included in this file is provided under the terms of the ISC license\r
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission\r
12    To use, copy, modify, and/or distribute this software for any purpose with or\r
13    without fee is hereby granted provided that the above copyright notice and\r
14    this permission notice appear in all copies.\r
15 \r
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER\r
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE\r
18    DISCLAIMED.\r
19 \r
20   ==============================================================================\r
21 */\r
22 \r
23 package com.juce.audioperformancetest;\r
24 \r
25 import android.app.Activity;\r
26 import android.app.AlertDialog;\r
27 import android.content.DialogInterface;\r
28 import android.content.Context;\r
29 import android.content.Intent;\r
30 import android.content.res.Configuration;\r
31 import android.content.pm.PackageInfo;\r
32 import android.content.pm.PackageManager;\r
33 import android.hardware.camera2.*;\r
34 import android.database.ContentObserver;\r
35 import android.media.session.*;\r
36 import android.media.MediaMetadata;\r
37 import android.net.http.SslError;\r
38 import android.net.Uri;\r
39 import android.os.Bundle;\r
40 import android.os.Looper;\r
41 import android.os.Handler;\r
42 import android.os.Message;\r
43 import android.os.ParcelUuid;\r
44 import android.os.Environment;\r
45 import android.view.*;\r
46 import android.view.inputmethod.BaseInputConnection;\r
47 import android.view.inputmethod.EditorInfo;\r
48 import android.view.inputmethod.InputConnection;\r
49 import android.view.inputmethod.InputMethodManager;\r
50 import android.graphics.*;\r
51 import android.text.ClipboardManager;\r
52 import android.text.InputType;\r
53 import android.util.DisplayMetrics;\r
54 import android.util.Log;\r
55 import android.util.Pair;\r
56 import android.webkit.SslErrorHandler;\r
57 import android.webkit.WebChromeClient;\r
58 import android.webkit.WebResourceError;\r
59 import android.webkit.WebResourceRequest;\r
60 import android.webkit.WebResourceResponse;\r
61 import android.webkit.WebView;\r
62 import android.webkit.WebViewClient;\r
63 import java.lang.Runnable;\r
64 import java.lang.ref.WeakReference;\r
65 import java.lang.reflect.*;\r
66 import java.util.*;\r
67 import java.io.*;\r
68 import java.net.URL;\r
69 import java.net.HttpURLConnection;\r
70 import android.media.AudioManager;\r
71 import android.Manifest;\r
72 import java.util.concurrent.CancellationException;\r
73 import java.util.concurrent.Future;\r
74 import java.util.concurrent.Executors;\r
75 import java.util.concurrent.ExecutorService;\r
76 import java.util.concurrent.ExecutionException;\r
77 import java.util.concurrent.TimeUnit;\r
78 import java.util.concurrent.Callable;\r
79 import java.util.concurrent.TimeoutException;\r
80 import java.util.concurrent.locks.ReentrantLock;\r
81 import java.util.concurrent.atomic.*;\r
82 \r
83 import android.media.midi.*;\r
84 import android.bluetooth.*;\r
85 import android.bluetooth.le.*;\r
86 \r
87 \r
88 //==============================================================================\r
89 public class AudioPerformanceTest   extends Activity\r
90 {\r
91     //==============================================================================\r
92     static\r
93     {\r
94         System.loadLibrary ("juce_jni");\r
95     }\r
96 \r
97     //==============================================================================\r
98     public boolean isPermissionDeclaredInManifest (int permissionID)\r
99     {\r
100         return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));\r
101     }\r
102 \r
103     public boolean isPermissionDeclaredInManifest (String permissionToCheck)\r
104     {\r
105         try\r
106         {\r
107             PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);\r
108 \r
109             if (info.requestedPermissions != null)\r
110                 for (String permission : info.requestedPermissions)\r
111                     if (permission.equals (permissionToCheck))\r
112                         return true;\r
113         }\r
114         catch (PackageManager.NameNotFoundException e)\r
115         {\r
116             Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());\r
117         }\r
118 \r
119         Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);\r
120         return false;\r
121     }\r
122 \r
123     //==============================================================================\r
124     // these have to match the values of enum PermissionID in C++ class RuntimePermissions:\r
125     private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1;\r
126     private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;\r
127     private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;\r
128     private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;\r
129     private static final int JUCE_PERMISSIONS_CAMERA = 5;\r
130 \r
131     private static String getAndroidPermissionName (int permissionID)\r
132     {\r
133         switch (permissionID)\r
134         {\r
135             case JUCE_PERMISSIONS_RECORD_AUDIO:           return Manifest.permission.RECORD_AUDIO;\r
136             case JUCE_PERMISSIONS_BLUETOOTH_MIDI:         return Manifest.permission.ACCESS_COARSE_LOCATION;\r
137                                                           // use string value as this is not defined in SDKs < 16\r
138             case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE:  return "android.permission.READ_EXTERNAL_STORAGE";\r
139             case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;\r
140             case JUCE_PERMISSIONS_CAMERA:                 return Manifest.permission.CAMERA;\r
141         }\r
142 \r
143         // unknown permission ID!\r
144         assert false;\r
145         return new String();\r
146     }\r
147 \r
148     public boolean isPermissionGranted (int permissionID)\r
149     {\r
150         return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;\r
151     }\r
152 \r
153     private Map<Integer, Long> permissionCallbackPtrMap;\r
154 \r
155     public void requestRuntimePermission (int permissionID, long ptrToCallback)\r
156     {\r
157         String permissionName = getAndroidPermissionName (permissionID);\r
158 \r
159         if (getApplicationContext().checkCallingOrSelfPermission (permissionName) != PackageManager.PERMISSION_GRANTED)\r
160         {\r
161             // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously\r
162             permissionCallbackPtrMap.put (permissionID, ptrToCallback);\r
163             requestPermissionsCompat (new String[]{permissionName}, permissionID);\r
164         }\r
165         else\r
166         {\r
167             // permissions were already granted before, we can call callback directly\r
168             androidRuntimePermissionsCallback (true, ptrToCallback);\r
169         }\r
170     }\r
171 \r
172     private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);\r
173 \r
174     @Override\r
175     public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults)\r
176     {\r
177         boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);\r
178 \r
179         if (! permissionsGranted)\r
180             Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID));\r
181 \r
182         Long ptrToCallback = permissionCallbackPtrMap.get (permissionID);\r
183         permissionCallbackPtrMap.remove (permissionID);\r
184         androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback);\r
185     }\r
186 \r
187     //==============================================================================\r
188     public interface JuceMidiPort\r
189     {\r
190         boolean isInputPort();\r
191 \r
192         // start, stop does nothing on an output port\r
193         void start();\r
194         void stop();\r
195 \r
196         void close();\r
197 \r
198         // send will do nothing on an input port\r
199         void sendMidi (byte[] msg, int offset, int count);\r
200     }\r
201 \r
202     //==============================================================================\r
203     //==============================================================================\r
204     public class BluetoothManager extends ScanCallback\r
205     {\r
206         BluetoothManager()\r
207         {\r
208         }\r
209 \r
210         public String[] getMidiBluetoothAddresses()\r
211         {\r
212             return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]);\r
213         }\r
214 \r
215         public String getHumanReadableStringForBluetoothAddress (String address)\r
216         {\r
217             BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);\r
218             return btDevice.getName();\r
219         }\r
220 \r
221         public int getBluetoothDeviceStatus (String address)\r
222         {\r
223             return getAndroidMidiDeviceManager().getBluetoothDeviceStatus (address);\r
224         }\r
225 \r
226         public void startStopScan (boolean shouldStart)\r
227         {\r
228             BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();\r
229 \r
230             if (bluetoothAdapter == null)\r
231             {\r
232                 Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter");\r
233                 return;\r
234             }\r
235 \r
236             BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();\r
237 \r
238             if (bluetoothLeScanner == null)\r
239             {\r
240                 Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner");\r
241                 return;\r
242             }\r
243 \r
244             if (shouldStart)\r
245             {\r
246                 ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();\r
247                 scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID));\r
248 \r
249                 ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();\r
250                 scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES)\r
251                                    .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER)\r
252                                    .setScanMode (ScanSettings.MATCH_MODE_STICKY);\r
253 \r
254                 bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()),\r
255                                               scanSettingsBuilder.build(),\r
256                                               this);\r
257             }\r
258             else\r
259             {\r
260                 bluetoothLeScanner.stopScan (this);\r
261             }\r
262         }\r
263 \r
264         public boolean pairBluetoothMidiDevice(String address)\r
265         {\r
266             BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);\r
267 \r
268             if (btDevice == null)\r
269             {\r
270                 Log.d ("JUCE", "failed to create buletooth device from address");\r
271                 return false;\r
272             }\r
273 \r
274             return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice);\r
275         }\r
276 \r
277         public void unpairBluetoothMidiDevice (String address)\r
278         {\r
279             getAndroidMidiDeviceManager().unpairBluetoothDevice (address);\r
280         }\r
281 \r
282         public void onScanFailed (int errorCode)\r
283         {\r
284         }\r
285 \r
286         public void onScanResult (int callbackType, ScanResult result)\r
287         {\r
288             if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES\r
289                  || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH)\r
290             {\r
291                 BluetoothDevice device = result.getDevice();\r
292 \r
293                 if (device != null)\r
294                     bluetoothMidiDevices.add (device.getAddress());\r
295             }\r
296 \r
297             if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST)\r
298             {\r
299                 Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST");\r
300                 BluetoothDevice device = result.getDevice();\r
301 \r
302                 if (device != null)\r
303                 {\r
304                     bluetoothMidiDevices.remove (device.getAddress());\r
305                     unpairBluetoothMidiDevice (device.getAddress());\r
306                 }\r
307             }\r
308         }\r
309 \r
310         public void onBatchScanResults (List<ScanResult> results)\r
311         {\r
312             for (ScanResult result : results)\r
313                 onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);\r
314         }\r
315 \r
316         private BluetoothLeScanner scanner;\r
317         private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";\r
318 \r
319         private HashSet<String> bluetoothMidiDevices = new HashSet<String>();\r
320     }\r
321 \r
322     public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort\r
323     {\r
324         private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp);\r
325 \r
326         public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse)\r
327         {\r
328             owner = mm;\r
329             androidPort = actualPort;\r
330             portPath = portPathToUse;\r
331             juceHost = hostToUse;\r
332             isConnected = false;\r
333         }\r
334 \r
335         @Override\r
336         protected void finalize() throws Throwable\r
337         {\r
338             close();\r
339             super.finalize();\r
340         }\r
341 \r
342         @Override\r
343         public boolean isInputPort()\r
344         {\r
345             return true;\r
346         }\r
347 \r
348         @Override\r
349         public void start()\r
350         {\r
351             if (owner != null && androidPort != null && ! isConnected) {\r
352                 androidPort.connect(this);\r
353                 isConnected = true;\r
354             }\r
355         }\r
356 \r
357         @Override\r
358         public void stop()\r
359         {\r
360             if (owner != null && androidPort != null && isConnected) {\r
361                 androidPort.disconnect(this);\r
362                 isConnected = false;\r
363             }\r
364         }\r
365 \r
366         @Override\r
367         public void close()\r
368         {\r
369             if (androidPort != null) {\r
370                 try {\r
371                     androidPort.close();\r
372                 } catch (IOException exception) {\r
373                     Log.d("JUCE", "IO Exception while closing port");\r
374                 }\r
375             }\r
376 \r
377             if (owner != null)\r
378                 owner.removePort (portPath);\r
379 \r
380             owner = null;\r
381             androidPort = null;\r
382         }\r
383 \r
384         @Override\r
385         public void onSend (byte[] msg, int offset, int count, long timestamp)\r
386         {\r
387             if (count > 0)\r
388                 handleReceive (juceHost, msg, offset, count, timestamp);\r
389         }\r
390 \r
391         @Override\r
392         public void onFlush()\r
393         {}\r
394 \r
395         @Override\r
396         public void sendMidi (byte[] msg, int offset, int count)\r
397         {\r
398         }\r
399 \r
400         MidiDeviceManager owner;\r
401         MidiOutputPort androidPort;\r
402         MidiPortPath portPath;\r
403         long juceHost;\r
404         boolean isConnected;\r
405     }\r
406 \r
407     public static class JuceMidiOutputPort implements JuceMidiPort\r
408     {\r
409         public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse)\r
410         {\r
411             owner = mm;\r
412             androidPort = actualPort;\r
413             portPath = portPathToUse;\r
414         }\r
415 \r
416         @Override\r
417         protected void finalize() throws Throwable\r
418         {\r
419             close();\r
420             super.finalize();\r
421         }\r
422 \r
423         @Override\r
424         public boolean isInputPort()\r
425         {\r
426             return false;\r
427         }\r
428 \r
429         @Override\r
430         public void start()\r
431         {\r
432         }\r
433 \r
434         @Override\r
435         public void stop()\r
436         {\r
437         }\r
438 \r
439         @Override\r
440         public void sendMidi (byte[] msg, int offset, int count)\r
441         {\r
442             if (androidPort != null)\r
443             {\r
444                 try {\r
445                     androidPort.send(msg, offset, count);\r
446                 } catch (IOException exception)\r
447                 {\r
448                     Log.d ("JUCE", "send midi had IO exception");\r
449                 }\r
450             }\r
451         }\r
452 \r
453         @Override\r
454         public void close()\r
455         {\r
456             if (androidPort != null) {\r
457                 try {\r
458                     androidPort.close();\r
459                 } catch (IOException exception) {\r
460                     Log.d("JUCE", "IO Exception while closing port");\r
461                 }\r
462             }\r
463 \r
464             if (owner != null)\r
465                 owner.removePort (portPath);\r
466 \r
467             owner = null;\r
468             androidPort = null;\r
469         }\r
470 \r
471         MidiDeviceManager owner;\r
472         MidiInputPort androidPort;\r
473         MidiPortPath portPath;\r
474     }\r
475 \r
476     private static class MidiPortPath extends Object\r
477     {\r
478         public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex)\r
479         {\r
480             deviceId = deviceIdToUse;\r
481             isInput = direction;\r
482             portIndex = androidIndex;\r
483 \r
484         }\r
485 \r
486         public int deviceId;\r
487         public int portIndex;\r
488         public boolean isInput;\r
489 \r
490         @Override\r
491         public int hashCode()\r
492         {\r
493             Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127));\r
494             return i.hashCode() * (isInput ? -1 : 1);\r
495         }\r
496 \r
497         @Override\r
498         public boolean equals (Object obj)\r
499         {\r
500             if (obj == null)\r
501                 return false;\r
502 \r
503             if (getClass() != obj.getClass())\r
504                 return false;\r
505 \r
506             MidiPortPath other = (MidiPortPath) obj;\r
507             return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId);\r
508         }\r
509     }\r
510 \r
511     //==============================================================================\r
512     public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener\r
513     {\r
514         //==============================================================================\r
515         private class DummyBluetoothGattCallback extends BluetoothGattCallback\r
516         {\r
517             public DummyBluetoothGattCallback (MidiDeviceManager mm)\r
518             {\r
519                 super();\r
520                 owner = mm;\r
521             }\r
522 \r
523             public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)\r
524             {\r
525                 if (newState == BluetoothProfile.STATE_CONNECTED)\r
526                 {\r
527                     gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);\r
528                     owner.pairBluetoothDeviceStepTwo (gatt.getDevice());\r
529                 }\r
530             }\r
531             public void onServicesDiscovered(BluetoothGatt gatt, int status) {}\r
532             public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}\r
533             public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}\r
534             public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {}\r
535             public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}\r
536             public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}\r
537             public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {}\r
538             public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {}\r
539             public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {}\r
540 \r
541             private MidiDeviceManager owner;\r
542         }\r
543 \r
544         //==============================================================================\r
545         private class MidiDeviceOpenTask extends java.util.TimerTask\r
546         {\r
547             public MidiDeviceOpenTask (MidiDeviceManager deviceManager, MidiDevice device, BluetoothGatt gattToUse)\r
548             {\r
549                 owner = deviceManager;\r
550                 midiDevice = device;\r
551                 btGatt = gattToUse;\r
552             }\r
553 \r
554             @Override\r
555             public boolean cancel()\r
556             {\r
557                 synchronized (MidiDeviceOpenTask.class)\r
558                 {\r
559                     owner = null;\r
560                     boolean retval = super.cancel();\r
561 \r
562                     if (btGatt != null)\r
563                     {\r
564                         btGatt.disconnect();\r
565                         btGatt.close();\r
566 \r
567                         btGatt = null;\r
568                     }\r
569 \r
570                     if (midiDevice != null)\r
571                     {\r
572                         try\r
573                         {\r
574                             midiDevice.close();\r
575                         }\r
576                         catch (IOException e)\r
577                         {}\r
578 \r
579                         midiDevice = null;\r
580                     }\r
581 \r
582                     return retval;\r
583                 }\r
584             }\r
585 \r
586             public String getBluetoothAddress()\r
587             {\r
588                 synchronized (MidiDeviceOpenTask.class)\r
589                 {\r
590                     if (midiDevice != null)\r
591                     {\r
592                         MidiDeviceInfo info = midiDevice.getInfo();\r
593                         if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)\r
594                         {\r
595                             BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);\r
596                             if (btDevice != null)\r
597                                 return btDevice.getAddress();\r
598                         }\r
599                     }\r
600                 }\r
601 \r
602                 return "";\r
603             }\r
604 \r
605             public BluetoothGatt getGatt() { return btGatt; }\r
606 \r
607             public int getID()\r
608             {\r
609                 return midiDevice.getInfo().getId();\r
610             }\r
611 \r
612             @Override\r
613             public void run()\r
614             {\r
615                 synchronized (MidiDeviceOpenTask.class)\r
616                 {\r
617                     if (owner != null && midiDevice != null)\r
618                         owner.onDeviceOpenedDelayed (midiDevice);\r
619                 }\r
620             }\r
621 \r
622             private MidiDeviceManager owner;\r
623             private MidiDevice midiDevice;\r
624             private BluetoothGatt btGatt;\r
625         }\r
626 \r
627         //==============================================================================\r
628         public MidiDeviceManager()\r
629         {\r
630             manager = (MidiManager) getSystemService (MIDI_SERVICE);\r
631 \r
632             if (manager == null)\r
633             {\r
634                 Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service");\r
635                 return;\r
636             }\r
637 \r
638             openPorts = new HashMap<MidiPortPath, WeakReference<JuceMidiPort>> ();\r
639             midiDevices = new ArrayList<Pair<MidiDevice,BluetoothGatt>>();\r
640             openTasks = new HashMap<Integer, MidiDeviceOpenTask>();\r
641             btDevicesPairing = new HashMap<String, BluetoothGatt>();\r
642 \r
643             MidiDeviceInfo[] foundDevices = manager.getDevices();\r
644             for (MidiDeviceInfo info : foundDevices)\r
645                 onDeviceAdded (info);\r
646 \r
647             manager.registerDeviceCallback (this, null);\r
648         }\r
649 \r
650         protected void finalize() throws Throwable\r
651         {\r
652             manager.unregisterDeviceCallback (this);\r
653 \r
654             synchronized (MidiDeviceManager.class)\r
655             {\r
656                 btDevicesPairing.clear();\r
657 \r
658                 for (Integer deviceID : openTasks.keySet())\r
659                     openTasks.get (deviceID).cancel();\r
660 \r
661                 openTasks = null;\r
662             }\r
663 \r
664             for (MidiPortPath key : openPorts.keySet())\r
665                 openPorts.get (key).get().close();\r
666 \r
667             openPorts = null;\r
668 \r
669             for (Pair<MidiDevice, BluetoothGatt> device : midiDevices)\r
670             {\r
671                 if (device.second != null)\r
672                 {\r
673                     device.second.disconnect();\r
674                     device.second.close();\r
675                 }\r
676 \r
677                 device.first.close();\r
678             }\r
679 \r
680             midiDevices.clear();\r
681 \r
682             super.finalize();\r
683         }\r
684 \r
685         public String[] getJuceAndroidMidiInputDevices()\r
686         {\r
687             return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT);\r
688         }\r
689 \r
690         public String[] getJuceAndroidMidiOutputDevices()\r
691         {\r
692             return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT);\r
693         }\r
694 \r
695         private String[] getJuceAndroidMidiDevices (int portType)\r
696         {\r
697             // only update the list when JUCE asks for a new list\r
698             synchronized (MidiDeviceManager.class)\r
699             {\r
700                 deviceInfos = getDeviceInfos();\r
701             }\r
702 \r
703             ArrayList<String> portNames = new ArrayList<String>();\r
704 \r
705             int index = 0;\r
706             for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index))\r
707                 portNames.add (getPortName (portInfo));\r
708 \r
709             String[] names = new String[portNames.size()];\r
710             return portNames.toArray (names);\r
711         }\r
712 \r
713         private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput)\r
714         {\r
715             synchronized (MidiDeviceManager.class)\r
716             {\r
717                 int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT);\r
718                 MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index);\r
719 \r
720                 if (portInfo != null)\r
721                 {\r
722                     // ports must be opened exclusively!\r
723                     if (openPorts.containsKey (portInfo))\r
724                         return null;\r
725 \r
726                     Pair<MidiDevice,BluetoothGatt> devicePair = getMidiDevicePairForId (portInfo.deviceId);\r
727 \r
728                     if (devicePair != null)\r
729                     {\r
730                         MidiDevice device = devicePair.first;\r
731                         if (device != null)\r
732                         {\r
733                             JuceMidiPort juceMidiPort = null;\r
734 \r
735                             if (isInput)\r
736                             {\r
737                                 MidiOutputPort outputPort = device.openOutputPort(portInfo.portIndex);\r
738 \r
739                                 if (outputPort != null)\r
740                                     juceMidiPort = new JuceMidiInputPort(this, outputPort, portInfo, host);\r
741                             }\r
742                             else\r
743                             {\r
744                                 MidiInputPort inputPort = device.openInputPort(portInfo.portIndex);\r
745 \r
746                                 if (inputPort != null)\r
747                                     juceMidiPort = new JuceMidiOutputPort(this, inputPort, portInfo);\r
748                             }\r
749 \r
750                             if (juceMidiPort != null)\r
751                             {\r
752                                 openPorts.put(portInfo, new WeakReference<JuceMidiPort>(juceMidiPort));\r
753 \r
754                                 return juceMidiPort;\r
755                             }\r
756                         }\r
757                     }\r
758                 }\r
759             }\r
760 \r
761             return null;\r
762         }\r
763 \r
764         public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)\r
765         {\r
766             return openMidiPortWithJuceIndex (index, host, true);\r
767         }\r
768 \r
769         public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)\r
770         {\r
771             return openMidiPortWithJuceIndex (index, 0, false);\r
772         }\r
773 \r
774         /* 0: unpaired, 1: paired, 2: pairing */\r
775         public int getBluetoothDeviceStatus (String address)\r
776         {\r
777             synchronized (MidiDeviceManager.class)\r
778             {\r
779                 if (! address.isEmpty())\r
780                 {\r
781                     if (findMidiDeviceForBluetoothAddress (address) != null)\r
782                         return 1;\r
783 \r
784                     if (btDevicesPairing.containsKey (address))\r
785                         return 2;\r
786 \r
787                     if (findOpenTaskForBluetoothAddress (address) != null)\r
788                         return 2;\r
789                 }\r
790             }\r
791 \r
792             return 0;\r
793         }\r
794 \r
795         public boolean pairBluetoothDevice (BluetoothDevice btDevice)\r
796         {\r
797             String btAddress = btDevice.getAddress();\r
798             if (btAddress.isEmpty())\r
799                 return false;\r
800 \r
801             synchronized (MidiDeviceManager.class)\r
802             {\r
803                 if (getBluetoothDeviceStatus (btAddress) != 0)\r
804                     return false;\r
805 \r
806 \r
807                 btDevicesPairing.put (btDevice.getAddress(), null);\r
808                 BluetoothGatt gatt = btDevice.connectGatt (getApplicationContext(), true, new DummyBluetoothGattCallback (this));\r
809 \r
810                 if (gatt != null)\r
811                 {\r
812                     btDevicesPairing.put (btDevice.getAddress(), gatt);\r
813                 }\r
814                 else\r
815                 {\r
816                     pairBluetoothDeviceStepTwo (btDevice);\r
817                 }\r
818             }\r
819 \r
820             return true;\r
821         }\r
822 \r
823         public void pairBluetoothDeviceStepTwo (BluetoothDevice btDevice)\r
824         {\r
825             manager.openBluetoothDevice(btDevice, this, null);\r
826         }\r
827 \r
828         public void unpairBluetoothDevice (String address)\r
829         {\r
830             if (address.isEmpty())\r
831                 return;\r
832 \r
833             synchronized (MidiDeviceManager.class)\r
834             {\r
835                 if (btDevicesPairing.containsKey (address))\r
836                 {\r
837                     BluetoothGatt gatt = btDevicesPairing.get (address);\r
838                     if (gatt != null)\r
839                     {\r
840                         gatt.disconnect();\r
841                         gatt.close();\r
842                     }\r
843 \r
844                     btDevicesPairing.remove (address);\r
845                 }\r
846 \r
847                 MidiDeviceOpenTask openTask = findOpenTaskForBluetoothAddress (address);\r
848                 if (openTask != null)\r
849                 {\r
850                     int deviceID = openTask.getID();\r
851                     openTask.cancel();\r
852                     openTasks.remove (deviceID);\r
853                 }\r
854 \r
855                 Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (address);\r
856                 if (midiDevicePair != null)\r
857                 {\r
858                     MidiDevice midiDevice = midiDevicePair.first;\r
859                     onDeviceRemoved (midiDevice.getInfo());\r
860 \r
861                     try {\r
862                         midiDevice.close();\r
863                     }\r
864                     catch (IOException exception)\r
865                     {\r
866                         Log.d ("JUCE", "IOException while closing midi device");\r
867                     }\r
868                 }\r
869             }\r
870         }\r
871 \r
872         private Pair<MidiDevice, BluetoothGatt> findMidiDeviceForBluetoothAddress (String address)\r
873         {\r
874             for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)\r
875             {\r
876                 MidiDeviceInfo info = midiDevice.first.getInfo();\r
877                 if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)\r
878                 {\r
879                     BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);\r
880                     if (btDevice != null && btDevice.getAddress().equals (address))\r
881                         return midiDevice;\r
882                 }\r
883             }\r
884 \r
885             return null;\r
886         }\r
887 \r
888         private MidiDeviceOpenTask findOpenTaskForBluetoothAddress (String address)\r
889         {\r
890             for (Integer deviceID : openTasks.keySet())\r
891             {\r
892                 MidiDeviceOpenTask openTask = openTasks.get (deviceID);\r
893                 if (openTask.getBluetoothAddress().equals (address))\r
894                     return openTask;\r
895             }\r
896 \r
897             return null;\r
898         }\r
899 \r
900         public void removePort (MidiPortPath path)\r
901         {\r
902             openPorts.remove (path);\r
903         }\r
904 \r
905         public String getInputPortNameForJuceIndex (int index)\r
906         {\r
907             MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index);\r
908             if (portInfo != null)\r
909                 return getPortName (portInfo);\r
910 \r
911             return "";\r
912         }\r
913 \r
914         public String getOutputPortNameForJuceIndex (int index)\r
915         {\r
916             MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index);\r
917             if (portInfo != null)\r
918                 return getPortName (portInfo);\r
919 \r
920             return "";\r
921         }\r
922 \r
923         public void onDeviceAdded (MidiDeviceInfo info)\r
924         {\r
925             // only add standard midi devices\r
926             if (info.getType() == info.TYPE_BLUETOOTH)\r
927                 return;\r
928 \r
929             manager.openDevice (info, this, null);\r
930         }\r
931 \r
932         public void onDeviceRemoved (MidiDeviceInfo info)\r
933         {\r
934             synchronized (MidiDeviceManager.class)\r
935             {\r
936                 Pair<MidiDevice, BluetoothGatt> devicePair = getMidiDevicePairForId (info.getId());\r
937 \r
938                 if (devicePair != null)\r
939                 {\r
940                     MidiDevice midiDevice = devicePair.first;\r
941                     BluetoothGatt gatt = devicePair.second;\r
942 \r
943                     // close all ports that use this device\r
944                     boolean removedPort = true;\r
945 \r
946                     while (removedPort == true)\r
947                     {\r
948                         removedPort = false;\r
949                         for (MidiPortPath key : openPorts.keySet())\r
950                         {\r
951                             if (key.deviceId == info.getId())\r
952                             {\r
953                                 openPorts.get(key).get().close();\r
954                                 removedPort = true;\r
955                                 break;\r
956                             }\r
957                         }\r
958                     }\r
959 \r
960                     if (gatt != null)\r
961                     {\r
962                         gatt.disconnect();\r
963                         gatt.close();\r
964                     }\r
965 \r
966                     midiDevices.remove (devicePair);\r
967                 }\r
968             }\r
969         }\r
970 \r
971         public void onDeviceStatusChanged (MidiDeviceStatus status)\r
972         {\r
973         }\r
974 \r
975         @Override\r
976         public void onDeviceOpened (MidiDevice theDevice)\r
977         {\r
978             synchronized (MidiDeviceManager.class)\r
979             {\r
980                 MidiDeviceInfo info = theDevice.getInfo();\r
981                 int deviceID = info.getId();\r
982                 BluetoothGatt gatt = null;\r
983                 boolean isBluetooth = false;\r
984 \r
985                 if (! openTasks.containsKey (deviceID))\r
986                 {\r
987                     if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)\r
988                     {\r
989                         isBluetooth = true;\r
990                         BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);\r
991                         if (btDevice != null)\r
992                         {\r
993                             String btAddress = btDevice.getAddress();\r
994                             if (btDevicesPairing.containsKey (btAddress))\r
995                             {\r
996                                 gatt = btDevicesPairing.get (btAddress);\r
997                                 btDevicesPairing.remove (btAddress);\r
998                             }\r
999                             else\r
1000                             {\r
1001                                 // unpair was called in the mean time\r
1002                                 try\r
1003                                 {\r
1004                                     Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());\r
1005                                     if (midiDevicePair != null)\r
1006                                     {\r
1007                                         gatt = midiDevicePair.second;\r
1008 \r
1009                                         if (gatt != null)\r
1010                                         {\r
1011                                             gatt.disconnect();\r
1012                                             gatt.close();\r
1013                                         }\r
1014                                     }\r
1015 \r
1016                                     theDevice.close();\r
1017                                 }\r
1018                                 catch (IOException e)\r
1019                                 {}\r
1020 \r
1021                                 return;\r
1022                             }\r
1023                         }\r
1024                     }\r
1025 \r
1026                     MidiDeviceOpenTask openTask = new MidiDeviceOpenTask (this, theDevice, gatt);\r
1027                     openTasks.put (deviceID, openTask);\r
1028 \r
1029                     new java.util.Timer().schedule (openTask, (isBluetooth ? 2000 : 100));\r
1030                 }\r
1031             }\r
1032         }\r
1033 \r
1034         public void onDeviceOpenedDelayed (MidiDevice theDevice)\r
1035         {\r
1036             synchronized (MidiDeviceManager.class)\r
1037             {\r
1038                 int deviceID = theDevice.getInfo().getId();\r
1039 \r
1040                 if (openTasks.containsKey (deviceID))\r
1041                 {\r
1042                     if (! midiDevices.contains(theDevice))\r
1043                     {\r
1044                         BluetoothGatt gatt = openTasks.get (deviceID).getGatt();\r
1045                         openTasks.remove (deviceID);\r
1046                         midiDevices.add (new Pair<MidiDevice,BluetoothGatt> (theDevice, gatt));\r
1047                     }\r
1048                 }\r
1049                 else\r
1050                 {\r
1051                     // unpair was called in the mean time\r
1052                     MidiDeviceInfo info = theDevice.getInfo();\r
1053                     BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);\r
1054                     if (btDevice != null)\r
1055                     {\r
1056                         String btAddress = btDevice.getAddress();\r
1057                         Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());\r
1058                         if (midiDevicePair != null)\r
1059                         {\r
1060                             BluetoothGatt gatt = midiDevicePair.second;\r
1061 \r
1062                             if (gatt != null)\r
1063                             {\r
1064                                 gatt.disconnect();\r
1065                                 gatt.close();\r
1066                             }\r
1067                         }\r
1068                     }\r
1069 \r
1070                     try\r
1071                     {\r
1072                         theDevice.close();\r
1073                     }\r
1074                     catch (IOException e)\r
1075                     {}\r
1076                 }\r
1077             }\r
1078         }\r
1079 \r
1080         public String getPortName(MidiPortPath path)\r
1081         {\r
1082             int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT);\r
1083 \r
1084             synchronized (MidiDeviceManager.class)\r
1085             {\r
1086                 for (MidiDeviceInfo info : deviceInfos)\r
1087                 {\r
1088                     int localIndex = 0;\r
1089                     if (info.getId() == path.deviceId)\r
1090                     {\r
1091                         for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())\r
1092                         {\r
1093                             int portType = portInfo.getType();\r
1094                             if (portType == portTypeToFind)\r
1095                             {\r
1096                                 int portIndex = portInfo.getPortNumber();\r
1097                                 if (portIndex == path.portIndex)\r
1098                                 {\r
1099                                     String portName = portInfo.getName();\r
1100                                     if (portName.isEmpty())\r
1101                                         portName = (String) info.getProperties().get(info.PROPERTY_NAME);\r
1102 \r
1103                                     return portName;\r
1104                                 }\r
1105                             }\r
1106                         }\r
1107                     }\r
1108                 }\r
1109             }\r
1110 \r
1111             return "";\r
1112         }\r
1113 \r
1114         public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex)\r
1115         {\r
1116             int portIdx = 0;\r
1117             for (MidiDeviceInfo info : deviceInfos)\r
1118             {\r
1119                 for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())\r
1120                 {\r
1121                     if (portInfo.getType() == portType)\r
1122                     {\r
1123                         if (portIdx == juceIndex)\r
1124                             return new MidiPortPath (info.getId(),\r
1125                                     (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),\r
1126                                     portInfo.getPortNumber());\r
1127 \r
1128                         portIdx++;\r
1129                     }\r
1130                 }\r
1131             }\r
1132 \r
1133             return null;\r
1134         }\r
1135 \r
1136         private MidiDeviceInfo[] getDeviceInfos()\r
1137         {\r
1138             synchronized (MidiDeviceManager.class)\r
1139             {\r
1140                 MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size()];\r
1141 \r
1142                 int idx = 0;\r
1143                 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)\r
1144                     infos[idx++] = midiDevice.first.getInfo();\r
1145 \r
1146                 return infos;\r
1147             }\r
1148         }\r
1149 \r
1150         private Pair<MidiDevice, BluetoothGatt> getMidiDevicePairForId (int deviceId)\r
1151         {\r
1152             synchronized (MidiDeviceManager.class)\r
1153             {\r
1154                 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)\r
1155                     if (midiDevice.first.getInfo().getId() == deviceId)\r
1156                         return midiDevice;\r
1157             }\r
1158 \r
1159             return null;\r
1160         }\r
1161 \r
1162         private MidiManager manager;\r
1163         private HashMap<String, BluetoothGatt> btDevicesPairing;\r
1164         private HashMap<Integer, MidiDeviceOpenTask> openTasks;\r
1165         private ArrayList<Pair<MidiDevice, BluetoothGatt>> midiDevices;\r
1166         private MidiDeviceInfo[] deviceInfos;\r
1167         private HashMap<MidiPortPath, WeakReference<JuceMidiPort>> openPorts;\r
1168     }\r
1169 \r
1170     public MidiDeviceManager getAndroidMidiDeviceManager()\r
1171     {\r
1172         if (getSystemService (MIDI_SERVICE) == null)\r
1173             return null;\r
1174 \r
1175         synchronized (AudioPerformanceTest.class)\r
1176         {\r
1177             if (midiDeviceManager == null)\r
1178                 midiDeviceManager = new MidiDeviceManager();\r
1179         }\r
1180 \r
1181         return midiDeviceManager;\r
1182     }\r
1183 \r
1184     public BluetoothManager getAndroidBluetoothManager()\r
1185     {\r
1186         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();\r
1187 \r
1188         if (adapter == null)\r
1189             return null;\r
1190 \r
1191         if (adapter.getBluetoothLeScanner() == null)\r
1192             return null;\r
1193 \r
1194         synchronized (AudioPerformanceTest.class)\r
1195         {\r
1196             if (bluetoothManager == null)\r
1197                 bluetoothManager = new BluetoothManager();\r
1198         }\r
1199 \r
1200         return bluetoothManager;\r
1201     }\r
1202 \r
1203     //==============================================================================\r
1204     @Override\r
1205     public void onCreate (Bundle savedInstanceState)\r
1206     {\r
1207         super.onCreate (savedInstanceState);\r
1208 \r
1209         isScreenSaverEnabled = true;\r
1210         hideActionBar();\r
1211         viewHolder = new ViewHolder (this);\r
1212         setContentView (viewHolder);\r
1213 \r
1214         setVolumeControlStream (AudioManager.STREAM_MUSIC);\r
1215 \r
1216         permissionCallbackPtrMap = new HashMap<Integer, Long>();\r
1217         appPausedResumedListeners = new HashMap<Long, AppPausedResumedListener>();\r
1218     }\r
1219 \r
1220     @Override\r
1221     protected void onDestroy()\r
1222     {\r
1223         quitApp();\r
1224         super.onDestroy();\r
1225 \r
1226         clearDataCache();\r
1227     }\r
1228 \r
1229     @Override\r
1230     protected void onPause()\r
1231     {\r
1232         suspendApp();\r
1233 \r
1234         Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);\r
1235 \r
1236         for (Long k : keys)\r
1237             appPausedResumedListeners.get (k).appPaused();\r
1238 \r
1239         try\r
1240         {\r
1241             Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down\r
1242                                  // openGL glitches when pausing/resuming apps..\r
1243         } catch (InterruptedException e) {}\r
1244 \r
1245         super.onPause();\r
1246     }\r
1247 \r
1248     @Override\r
1249     protected void onResume()\r
1250     {\r
1251         super.onResume();\r
1252         resumeApp();\r
1253 \r
1254         Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);\r
1255 \r
1256         for (Long k : keys)\r
1257             appPausedResumedListeners.get (k).appResumed();\r
1258     }\r
1259 \r
1260     @Override\r
1261     public void onConfigurationChanged (Configuration cfg)\r
1262     {\r
1263         super.onConfigurationChanged (cfg);\r
1264         setContentView (viewHolder);\r
1265     }\r
1266 \r
1267     private void callAppLauncher()\r
1268     {\r
1269         launchApp (getApplicationInfo().publicSourceDir,\r
1270                    getApplicationInfo().dataDir);\r
1271     }\r
1272 \r
1273     // Need to override this as the default implementation always finishes the activity.\r
1274     @Override\r
1275     public void onBackPressed()\r
1276     {\r
1277         ComponentPeerView focusedView = getViewWithFocusOrDefaultView();\r
1278 \r
1279         if (focusedView == null)\r
1280             return;\r
1281 \r
1282         focusedView.backButtonPressed();\r
1283     }\r
1284 \r
1285     private ComponentPeerView getViewWithFocusOrDefaultView()\r
1286     {\r
1287         for (int i = 0; i < viewHolder.getChildCount(); ++i)\r
1288         {\r
1289             if (viewHolder.getChildAt (i).hasFocus())\r
1290                 return (ComponentPeerView) viewHolder.getChildAt (i);\r
1291         }\r
1292 \r
1293         if (viewHolder.getChildCount() > 0)\r
1294             return (ComponentPeerView) viewHolder.getChildAt (0);\r
1295 \r
1296         return null;\r
1297     }\r
1298 \r
1299     //==============================================================================\r
1300     private void hideActionBar()\r
1301     {\r
1302         // get "getActionBar" method\r
1303         java.lang.reflect.Method getActionBarMethod = null;\r
1304         try\r
1305         {\r
1306             getActionBarMethod = this.getClass().getMethod ("getActionBar");\r
1307         }\r
1308         catch (SecurityException e)     { return; }\r
1309         catch (NoSuchMethodException e) { return; }\r
1310         if (getActionBarMethod == null) return;\r
1311 \r
1312         // invoke "getActionBar" method\r
1313         Object actionBar = null;\r
1314         try\r
1315         {\r
1316             actionBar = getActionBarMethod.invoke (this);\r
1317         }\r
1318         catch (java.lang.IllegalArgumentException e) { return; }\r
1319         catch (java.lang.IllegalAccessException e) { return; }\r
1320         catch (java.lang.reflect.InvocationTargetException e) { return; }\r
1321         if (actionBar == null) return;\r
1322 \r
1323         // get "hide" method\r
1324         java.lang.reflect.Method actionBarHideMethod = null;\r
1325         try\r
1326         {\r
1327             actionBarHideMethod = actionBar.getClass().getMethod ("hide");\r
1328         }\r
1329         catch (SecurityException e)     { return; }\r
1330         catch (NoSuchMethodException e) { return; }\r
1331         if (actionBarHideMethod == null) return;\r
1332 \r
1333         // invoke "hide" method\r
1334         try\r
1335         {\r
1336             actionBarHideMethod.invoke (actionBar);\r
1337         }\r
1338         catch (java.lang.IllegalArgumentException e) {}\r
1339         catch (java.lang.IllegalAccessException e) {}\r
1340         catch (java.lang.reflect.InvocationTargetException e) {}\r
1341     }\r
1342 \r
1343     void requestPermissionsCompat (String[] permissions, int requestCode)\r
1344     {\r
1345         Method requestPermissionsMethod = null;\r
1346         try\r
1347         {\r
1348             requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",\r
1349                                                                   String[].class, int.class);\r
1350         }\r
1351         catch (SecurityException e)     { return; }\r
1352         catch (NoSuchMethodException e) { return; }\r
1353         if (requestPermissionsMethod == null) return;\r
1354 \r
1355         try\r
1356         {\r
1357             requestPermissionsMethod.invoke (this, permissions, requestCode);\r
1358         }\r
1359         catch (java.lang.IllegalArgumentException e) {}\r
1360         catch (java.lang.IllegalAccessException e) {}\r
1361         catch (java.lang.reflect.InvocationTargetException e) {}\r
1362     }\r
1363 \r
1364     //==============================================================================\r
1365     private native void launchApp (String appFile, String appDataDir);\r
1366     private native void quitApp();\r
1367     private native void suspendApp();\r
1368     private native void resumeApp();\r
1369     private native void setScreenSize (int screenWidth, int screenHeight, int dpi);\r
1370     private native void appActivityResult (int requestCode, int resultCode, Intent data);\r
1371     private native void appNewIntent (Intent intent);\r
1372 \r
1373     //==============================================================================\r
1374     private ViewHolder viewHolder;\r
1375     private MidiDeviceManager midiDeviceManager = null;\r
1376     private BluetoothManager bluetoothManager = null;\r
1377     private boolean isScreenSaverEnabled;\r
1378     private java.util.Timer keepAliveTimer;\r
1379 \r
1380     public final ComponentPeerView createNewView (boolean opaque, long host)\r
1381     {\r
1382         ComponentPeerView v = new ComponentPeerView (this, opaque, host);\r
1383         viewHolder.addView (v);\r
1384         addAppPausedResumedListener (v, host);\r
1385         return v;\r
1386     }\r
1387 \r
1388     public final void deleteView (ComponentPeerView view)\r
1389     {\r
1390         removeAppPausedResumedListener (view, view.host);\r
1391 \r
1392         view.host = 0;\r
1393 \r
1394         ViewGroup group = (ViewGroup) (view.getParent());\r
1395 \r
1396         if (group != null)\r
1397             group.removeView (view);\r
1398     }\r
1399 \r
1400     public final void deleteNativeSurfaceView (NativeSurfaceView view)\r
1401     {\r
1402         ViewGroup group = (ViewGroup) (view.getParent());\r
1403 \r
1404         if (group != null)\r
1405             group.removeView (view);\r
1406     }\r
1407 \r
1408     final class ViewHolder  extends ViewGroup\r
1409     {\r
1410         public ViewHolder (Context context)\r
1411         {\r
1412             super (context);\r
1413             setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);\r
1414             setFocusable (false);\r
1415         }\r
1416 \r
1417         protected final void onLayout (boolean changed, int left, int top, int right, int bottom)\r
1418         {\r
1419             setScreenSize (getWidth(), getHeight(), getDPI());\r
1420 \r
1421             if (isFirstResize)\r
1422             {\r
1423                 isFirstResize = false;\r
1424                 callAppLauncher();\r
1425             }\r
1426         }\r
1427 \r
1428         private final int getDPI()\r
1429         {\r
1430             DisplayMetrics metrics = new DisplayMetrics();\r
1431             getWindowManager().getDefaultDisplay().getMetrics (metrics);\r
1432             return metrics.densityDpi;\r
1433         }\r
1434 \r
1435         private boolean isFirstResize = true;\r
1436     }\r
1437 \r
1438     public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)\r
1439     {\r
1440         canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);\r
1441     }\r
1442 \r
1443     //==============================================================================\r
1444     public final void setScreenSaver (boolean enabled)\r
1445     {\r
1446         if (isScreenSaverEnabled != enabled)\r
1447         {\r
1448             isScreenSaverEnabled = enabled;\r
1449 \r
1450             if (keepAliveTimer != null)\r
1451             {\r
1452                 keepAliveTimer.cancel();\r
1453                 keepAliveTimer = null;\r
1454             }\r
1455 \r
1456             if (enabled)\r
1457             {\r
1458                 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\r
1459             }\r
1460             else\r
1461             {\r
1462                 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\r
1463 \r
1464                 // If no user input is received after about 3 seconds, the OS will lower the\r
1465                 // task's priority, so this timer forces it to be kept active.\r
1466                 keepAliveTimer = new java.util.Timer();\r
1467 \r
1468                 keepAliveTimer.scheduleAtFixedRate (new TimerTask()\r
1469                 {\r
1470                     @Override\r
1471                     public void run()\r
1472                     {\r
1473                         android.app.Instrumentation instrumentation = new android.app.Instrumentation();\r
1474 \r
1475                         try\r
1476                         {\r
1477                             instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);\r
1478                         }\r
1479                         catch (Exception e)\r
1480                         {\r
1481                         }\r
1482                     }\r
1483                 }, 2000, 2000);\r
1484             }\r
1485         }\r
1486     }\r
1487 \r
1488     public final boolean getScreenSaver()\r
1489     {\r
1490         return isScreenSaverEnabled;\r
1491     }\r
1492 \r
1493     //==============================================================================\r
1494     public final String getClipboardContent()\r
1495     {\r
1496         ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);\r
1497 \r
1498         CharSequence content = clipboard.getText();\r
1499         return content != null ? content.toString() : new String();\r
1500     }\r
1501 \r
1502     public final void setClipboardContent (String newText)\r
1503     {\r
1504         ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);\r
1505         clipboard.setText (newText);\r
1506     }\r
1507 \r
1508     //==============================================================================\r
1509     public final void showMessageBox (String title, String message, final long callback)\r
1510     {\r
1511         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
1512         builder.setTitle (title)\r
1513                .setMessage (message)\r
1514                .setCancelable (true)\r
1515                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
1516                     {\r
1517                         public void onCancel (DialogInterface dialog)\r
1518                         {\r
1519                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1520                         }\r
1521                     })\r
1522                .setPositiveButton ("OK", new DialogInterface.OnClickListener()\r
1523                     {\r
1524                         public void onClick (DialogInterface dialog, int id)\r
1525                         {\r
1526                             dialog.dismiss();\r
1527                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1528                         }\r
1529                     });\r
1530 \r
1531         builder.create().show();\r
1532     }\r
1533 \r
1534     public final void showOkCancelBox (String title, String message, final long callback,\r
1535                                        String okButtonText, String cancelButtonText)\r
1536     {\r
1537         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
1538         builder.setTitle (title)\r
1539                .setMessage (message)\r
1540                .setCancelable (true)\r
1541                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
1542                     {\r
1543                         public void onCancel (DialogInterface dialog)\r
1544                         {\r
1545                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1546                         }\r
1547                     })\r
1548                .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()\r
1549                     {\r
1550                         public void onClick (DialogInterface dialog, int id)\r
1551                         {\r
1552                             dialog.dismiss();\r
1553                             AudioPerformanceTest.this.alertDismissed (callback, 1);\r
1554                         }\r
1555                     })\r
1556                .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()\r
1557                     {\r
1558                         public void onClick (DialogInterface dialog, int id)\r
1559                         {\r
1560                             dialog.dismiss();\r
1561                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1562                         }\r
1563                     });\r
1564 \r
1565         builder.create().show();\r
1566     }\r
1567 \r
1568     public final void showYesNoCancelBox (String title, String message, final long callback)\r
1569     {\r
1570         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
1571         builder.setTitle (title)\r
1572                .setMessage (message)\r
1573                .setCancelable (true)\r
1574                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
1575                     {\r
1576                         public void onCancel (DialogInterface dialog)\r
1577                         {\r
1578                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1579                         }\r
1580                     })\r
1581                .setPositiveButton ("Yes", new DialogInterface.OnClickListener()\r
1582                     {\r
1583                         public void onClick (DialogInterface dialog, int id)\r
1584                         {\r
1585                             dialog.dismiss();\r
1586                             AudioPerformanceTest.this.alertDismissed (callback, 1);\r
1587                         }\r
1588                     })\r
1589                .setNegativeButton ("No", new DialogInterface.OnClickListener()\r
1590                     {\r
1591                         public void onClick (DialogInterface dialog, int id)\r
1592                         {\r
1593                             dialog.dismiss();\r
1594                             AudioPerformanceTest.this.alertDismissed (callback, 2);\r
1595                         }\r
1596                     })\r
1597                .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()\r
1598                     {\r
1599                         public void onClick (DialogInterface dialog, int id)\r
1600                         {\r
1601                             dialog.dismiss();\r
1602                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1603                         }\r
1604                     });\r
1605 \r
1606         builder.create().show();\r
1607     }\r
1608 \r
1609     public native void alertDismissed (long callback, int id);\r
1610 \r
1611     //==============================================================================\r
1612     public interface AppPausedResumedListener\r
1613     {\r
1614         void appPaused();\r
1615         void appResumed();\r
1616     }\r
1617 \r
1618     private Map<Long, AppPausedResumedListener> appPausedResumedListeners;\r
1619 \r
1620     public void addAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)\r
1621     {\r
1622         appPausedResumedListeners.put (new Long (listenerHost), l);\r
1623     }\r
1624 \r
1625     public void removeAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)\r
1626     {\r
1627         appPausedResumedListeners.remove (new Long (listenerHost));\r
1628     }\r
1629 \r
1630     //==============================================================================\r
1631     public final class ComponentPeerView extends ViewGroup\r
1632                                          implements View.OnFocusChangeListener, AppPausedResumedListener\r
1633     {\r
1634         public ComponentPeerView (Context context, boolean opaque_, long host)\r
1635         {\r
1636             super (context);\r
1637             this.host = host;\r
1638             setWillNotDraw (false);\r
1639             opaque = opaque_;\r
1640 \r
1641             setFocusable (true);\r
1642             setFocusableInTouchMode (true);\r
1643             setOnFocusChangeListener (this);\r
1644 \r
1645             // swap red and blue colours to match internal opengl texture format\r
1646             ColorMatrix colorMatrix = new ColorMatrix();\r
1647 \r
1648             float[] colorTransform = { 0,    0,    1.0f, 0,    0,\r
1649                                        0,    1.0f, 0,    0,    0,\r
1650                                        1.0f, 0,    0,    0,    0,\r
1651                                        0,    0,    0,    1.0f, 0 };\r
1652 \r
1653             colorMatrix.set (colorTransform);\r
1654             paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));\r
1655 \r
1656             java.lang.reflect.Method method = null;\r
1657 \r
1658             try\r
1659             {\r
1660                 method = getClass().getMethod ("setLayerType", int.class, Paint.class);\r
1661             }\r
1662             catch (SecurityException e)     {}\r
1663             catch (NoSuchMethodException e) {}\r
1664 \r
1665             if (method != null)\r
1666             {\r
1667                 try\r
1668                 {\r
1669                     int layerTypeNone = 0;\r
1670                     method.invoke (this, layerTypeNone, null);\r
1671                 }\r
1672                 catch (java.lang.IllegalArgumentException e) {}\r
1673                 catch (java.lang.IllegalAccessException e) {}\r
1674                 catch (java.lang.reflect.InvocationTargetException e) {}\r
1675             }\r
1676         }\r
1677 \r
1678         //==============================================================================\r
1679         private native void handlePaint (long host, Canvas canvas, Paint paint);\r
1680 \r
1681         @Override\r
1682         public void onDraw (Canvas canvas)\r
1683         {\r
1684             if (host == 0)\r
1685                 return;\r
1686 \r
1687             handlePaint (host, canvas, paint);\r
1688         }\r
1689 \r
1690         @Override\r
1691         public boolean isOpaque()\r
1692         {\r
1693             return opaque;\r
1694         }\r
1695 \r
1696         private boolean opaque;\r
1697         private long host;\r
1698         private Paint paint = new Paint();\r
1699 \r
1700         //==============================================================================\r
1701         private native void handleMouseDown (long host, int index, float x, float y, long time);\r
1702         private native void handleMouseDrag (long host, int index, float x, float y, long time);\r
1703         private native void handleMouseUp   (long host, int index, float x, float y, long time);\r
1704 \r
1705         @Override\r
1706         public boolean onTouchEvent (MotionEvent event)\r
1707         {\r
1708             if (host == 0)\r
1709                 return false;\r
1710 \r
1711             int action = event.getAction();\r
1712             long time = event.getEventTime();\r
1713 \r
1714             switch (action & MotionEvent.ACTION_MASK)\r
1715             {\r
1716                 case MotionEvent.ACTION_DOWN:\r
1717                     handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);\r
1718                     return true;\r
1719 \r
1720                 case MotionEvent.ACTION_CANCEL:\r
1721                 case MotionEvent.ACTION_UP:\r
1722                     handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);\r
1723                     return true;\r
1724 \r
1725                 case MotionEvent.ACTION_MOVE:\r
1726                 {\r
1727                     int n = event.getPointerCount();\r
1728                     for (int i = 0; i < n; ++i)\r
1729                         handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
1730 \r
1731                     return true;\r
1732                 }\r
1733 \r
1734                 case MotionEvent.ACTION_POINTER_UP:\r
1735                 {\r
1736                     int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;\r
1737                     handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
1738                     return true;\r
1739                 }\r
1740 \r
1741                 case MotionEvent.ACTION_POINTER_DOWN:\r
1742                 {\r
1743                     int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;\r
1744                     handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
1745                     return true;\r
1746                 }\r
1747 \r
1748                 default:\r
1749                     break;\r
1750             }\r
1751 \r
1752             return false;\r
1753         }\r
1754 \r
1755         //==============================================================================\r
1756         private native void handleKeyDown (long host, int keycode, int textchar);\r
1757         private native void handleKeyUp (long host, int keycode, int textchar);\r
1758         private native void handleBackButton (long host);\r
1759         private native void handleKeyboardHidden (long host);\r
1760 \r
1761         public void showKeyboard (String type)\r
1762         {\r
1763             InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);\r
1764 \r
1765             if (imm != null)\r
1766             {\r
1767                 if (type.length() > 0)\r
1768                 {\r
1769                     imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);\r
1770                     imm.setInputMethod (getWindowToken(), type);\r
1771                     keyboardDismissListener.startListening();\r
1772                 }\r
1773                 else\r
1774                 {\r
1775                     imm.hideSoftInputFromWindow (getWindowToken(), 0);\r
1776                     keyboardDismissListener.stopListening();\r
1777                 }\r
1778             }\r
1779         }\r
1780 \r
1781         public void backButtonPressed()\r
1782         {\r
1783             if (host == 0)\r
1784                 return;\r
1785 \r
1786             handleBackButton (host);\r
1787         }\r
1788 \r
1789         @Override\r
1790         public boolean onKeyDown (int keyCode, KeyEvent event)\r
1791         {\r
1792             if (host == 0)\r
1793                 return false;\r
1794 \r
1795             switch (keyCode)\r
1796             {\r
1797                 case KeyEvent.KEYCODE_VOLUME_UP:\r
1798                 case KeyEvent.KEYCODE_VOLUME_DOWN:\r
1799                     return super.onKeyDown (keyCode, event);\r
1800                 case KeyEvent.KEYCODE_BACK:\r
1801                 {\r
1802                     ((Activity) getContext()).onBackPressed();\r
1803                     return true;\r
1804                 }\r
1805 \r
1806                 default:\r
1807                     break;\r
1808             }\r
1809 \r
1810             handleKeyDown (host, keyCode, event.getUnicodeChar());\r
1811             return true;\r
1812         }\r
1813 \r
1814         @Override\r
1815         public boolean onKeyUp (int keyCode, KeyEvent event)\r
1816         {\r
1817             if (host == 0)\r
1818                 return false;\r
1819 \r
1820             handleKeyUp (host, keyCode, event.getUnicodeChar());\r
1821             return true;\r
1822         }\r
1823 \r
1824         @Override\r
1825         public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)\r
1826         {\r
1827             if (host == 0)\r
1828                 return false;\r
1829 \r
1830             if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)\r
1831                 return super.onKeyMultiple (keyCode, count, event);\r
1832 \r
1833             if (event.getCharacters() != null)\r
1834             {\r
1835                 int utf8Char = event.getCharacters().codePointAt (0);\r
1836                 handleKeyDown (host, utf8Char, utf8Char);\r
1837                 return true;\r
1838             }\r
1839 \r
1840             return false;\r
1841         }\r
1842 \r
1843         //==============================================================================\r
1844         private final class KeyboardDismissListener\r
1845         {\r
1846             public KeyboardDismissListener (ComponentPeerView viewToUse)\r
1847             {\r
1848                 view = viewToUse;\r
1849             }\r
1850 \r
1851             private void startListening()\r
1852             {\r
1853                 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);\r
1854             }\r
1855 \r
1856             private void stopListening()\r
1857             {\r
1858                 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);\r
1859             }\r
1860 \r
1861             private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener\r
1862             {\r
1863                 TreeObserver()\r
1864                 {\r
1865                     keyboardShown = false;\r
1866                 }\r
1867 \r
1868                 @Override\r
1869                 public void onGlobalLayout()\r
1870                 {\r
1871                     Rect r = new Rect();\r
1872 \r
1873                     ViewGroup parentView = (ViewGroup) getParent();\r
1874 \r
1875                     if (parentView == null)\r
1876                         return;\r
1877 \r
1878                     parentView.getWindowVisibleDisplayFrame (r);\r
1879 \r
1880                     int diff = parentView.getHeight() - (r.bottom - r.top);\r
1881 \r
1882                     // Arbitrary threshold, surely keyboard would take more than 20 pix.\r
1883                     if (diff < 20 && keyboardShown)\r
1884                     {\r
1885                         keyboardShown = false;\r
1886                         handleKeyboardHidden (view.host);\r
1887                     }\r
1888 \r
1889                     if (! keyboardShown && diff > 20)\r
1890                         keyboardShown = true;\r
1891                 };\r
1892 \r
1893                 private boolean keyboardShown;\r
1894             };\r
1895 \r
1896             private ComponentPeerView view;\r
1897             private TreeObserver viewTreeObserver = new TreeObserver();\r
1898         }\r
1899 \r
1900         private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);\r
1901 \r
1902         // this is here to make keyboard entry work on a Galaxy Tab2 10.1\r
1903         @Override\r
1904         public InputConnection onCreateInputConnection (EditorInfo outAttrs)\r
1905         {\r
1906             outAttrs.actionLabel = "";\r
1907             outAttrs.hintText = "";\r
1908             outAttrs.initialCapsMode = 0;\r
1909             outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;\r
1910             outAttrs.label = "";\r
1911             outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;\r
1912             outAttrs.inputType = InputType.TYPE_NULL;\r
1913 \r
1914             return new BaseInputConnection (this, false);\r
1915         }\r
1916 \r
1917         //==============================================================================\r
1918         @Override\r
1919         protected void onSizeChanged (int w, int h, int oldw, int oldh)\r
1920         {\r
1921             if (host == 0)\r
1922                 return;\r
1923 \r
1924             super.onSizeChanged (w, h, oldw, oldh);\r
1925             viewSizeChanged (host);\r
1926         }\r
1927 \r
1928         @Override\r
1929         protected void onLayout (boolean changed, int left, int top, int right, int bottom)\r
1930         {\r
1931             for (int i = getChildCount(); --i >= 0;)\r
1932                 requestTransparentRegion (getChildAt (i));\r
1933         }\r
1934 \r
1935         private native void viewSizeChanged (long host);\r
1936 \r
1937         @Override\r
1938         public void onFocusChange (View v, boolean hasFocus)\r
1939         {\r
1940             if (host == 0)\r
1941                 return;\r
1942 \r
1943             if (v == this)\r
1944                 focusChanged (host, hasFocus);\r
1945         }\r
1946 \r
1947         private native void focusChanged (long host, boolean hasFocus);\r
1948 \r
1949         public void setViewName (String newName)    {}\r
1950 \r
1951         public void setSystemUiVisibilityCompat (int visibility)\r
1952         {\r
1953             Method systemUIVisibilityMethod = null;\r
1954             try\r
1955             {\r
1956                 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);\r
1957             }\r
1958             catch (SecurityException e)     { return; }\r
1959             catch (NoSuchMethodException e) { return; }\r
1960             if (systemUIVisibilityMethod == null) return;\r
1961 \r
1962             try\r
1963             {\r
1964                 systemUIVisibilityMethod.invoke (this, visibility);\r
1965             }\r
1966             catch (java.lang.IllegalArgumentException e) {}\r
1967             catch (java.lang.IllegalAccessException e) {}\r
1968             catch (java.lang.reflect.InvocationTargetException e) {}\r
1969         }\r
1970 \r
1971         public boolean isVisible()                  { return getVisibility() == VISIBLE; }\r
1972         public void setVisible (boolean b)          { setVisibility (b ? VISIBLE : INVISIBLE); }\r
1973 \r
1974         public boolean containsPoint (int x, int y)\r
1975         {\r
1976             return true; //xxx needs to check overlapping views\r
1977         }\r
1978 \r
1979         //==============================================================================\r
1980         private native void handleAppPaused (long host);\r
1981         private native void handleAppResumed (long host);\r
1982 \r
1983         @Override\r
1984         public void appPaused()\r
1985         {\r
1986             if (host == 0)\r
1987                 return;\r
1988 \r
1989             handleAppPaused (host);\r
1990         }\r
1991 \r
1992         @Override\r
1993         public void appResumed()\r
1994         {\r
1995             if (host == 0)\r
1996                 return;\r
1997 \r
1998             // Ensure that navigation/status bar visibility is correctly restored.\r
1999             handleAppResumed (host);\r
2000         }\r
2001     }\r
2002 \r
2003     //==============================================================================\r
2004     public static class NativeSurfaceView    extends SurfaceView\r
2005                                           implements SurfaceHolder.Callback\r
2006     {\r
2007         private long nativeContext = 0;\r
2008         private boolean forVideo;\r
2009 \r
2010         NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)\r
2011         {\r
2012             super (context);\r
2013             nativeContext = nativeContextPtr;\r
2014             forVideo = createdForVideo;\r
2015         }\r
2016 \r
2017         public Surface getNativeSurface()\r
2018         {\r
2019             Surface retval = null;\r
2020 \r
2021             SurfaceHolder holder = getHolder();\r
2022             if (holder != null)\r
2023                 retval = holder.getSurface();\r
2024 \r
2025             return retval;\r
2026         }\r
2027 \r
2028         //==============================================================================\r
2029         @Override\r
2030         public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)\r
2031         {\r
2032             if (forVideo)\r
2033                 surfaceChangedNativeVideo (nativeContext, holder, format, width, height);\r
2034             else\r
2035                 surfaceChangedNative (nativeContext, holder, format, width, height);\r
2036         }\r
2037 \r
2038         @Override\r
2039         public void surfaceCreated (SurfaceHolder holder)\r
2040         {\r
2041             if (forVideo)\r
2042                 surfaceCreatedNativeVideo (nativeContext, holder);\r
2043             else\r
2044                 surfaceCreatedNative (nativeContext, holder);\r
2045         }\r
2046 \r
2047         @Override\r
2048         public void surfaceDestroyed (SurfaceHolder holder)\r
2049         {\r
2050             if (forVideo)\r
2051                 surfaceDestroyedNativeVideo (nativeContext, holder);\r
2052             else\r
2053                 surfaceDestroyedNative (nativeContext, holder);\r
2054         }\r
2055 \r
2056         @Override\r
2057         protected void dispatchDraw (Canvas canvas)\r
2058         {\r
2059             super.dispatchDraw (canvas);\r
2060 \r
2061             if (forVideo)\r
2062                 dispatchDrawNativeVideo (nativeContext, canvas);\r
2063             else\r
2064                 dispatchDrawNative (nativeContext, canvas);\r
2065         }\r
2066 \r
2067         //==============================================================================\r
2068         @Override\r
2069         protected void onAttachedToWindow()\r
2070         {\r
2071             super.onAttachedToWindow();\r
2072             getHolder().addCallback (this);\r
2073         }\r
2074 \r
2075         @Override\r
2076         protected void onDetachedFromWindow()\r
2077         {\r
2078             super.onDetachedFromWindow();\r
2079             getHolder().removeCallback (this);\r
2080         }\r
2081 \r
2082         //==============================================================================\r
2083         private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);\r
2084         private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);\r
2085         private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);\r
2086         private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,\r
2087                                                   int format, int width, int height);\r
2088 \r
2089         private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);\r
2090         private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);\r
2091         private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);\r
2092         private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,\r
2093                                                        int format, int width, int height);\r
2094     }\r
2095 \r
2096     public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)\r
2097     {\r
2098         return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);\r
2099     }\r
2100 \r
2101     //==============================================================================\r
2102     public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)\r
2103     {\r
2104         Path p = new Path();\r
2105 \r
2106         char[] str = { glyph1, glyph2 };\r
2107         paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);\r
2108 \r
2109         RectF boundsF = new RectF();\r
2110         p.computeBounds (boundsF, true);\r
2111         matrix.mapRect (boundsF);\r
2112 \r
2113         boundsF.roundOut (bounds);\r
2114         bounds.left--;\r
2115         bounds.right++;\r
2116 \r
2117         final int w = bounds.width();\r
2118         final int h = Math.max (1, bounds.height());\r
2119 \r
2120         Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);\r
2121 \r
2122         Canvas c = new Canvas (bm);\r
2123         matrix.postTranslate (-bounds.left, -bounds.top);\r
2124         c.setMatrix (matrix);\r
2125         c.drawPath (p, paint);\r
2126 \r
2127         final int sizeNeeded = w * h;\r
2128         if (cachedRenderArray.length < sizeNeeded)\r
2129             cachedRenderArray = new int [sizeNeeded];\r
2130 \r
2131         bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);\r
2132         bm.recycle();\r
2133         return cachedRenderArray;\r
2134     }\r
2135 \r
2136     private int[] cachedRenderArray = new int [256];\r
2137 \r
2138     //==============================================================================\r
2139     public static class NativeInvocationHandler implements InvocationHandler\r
2140     {\r
2141         public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)\r
2142         {\r
2143             activity = activityToUse;\r
2144             nativeContext = nativeContextRef;\r
2145         }\r
2146 \r
2147         public void nativeContextDeleted()\r
2148         {\r
2149             nativeContext = 0;\r
2150         }\r
2151 \r
2152         @Override\r
2153         public void finalize()\r
2154         {\r
2155             activity.runOnUiThread (new Runnable()\r
2156                                     {\r
2157                                         @Override\r
2158                                         public void run()\r
2159                                         {\r
2160                                             if (nativeContext != 0)\r
2161                                                 dispatchFinalize (nativeContext);\r
2162                                         }\r
2163                                     });\r
2164         }\r
2165 \r
2166         @Override\r
2167         public Object invoke (Object proxy, Method method, Object[] args) throws Throwable\r
2168         {\r
2169             return dispatchInvoke (nativeContext, proxy, method, args);\r
2170         }\r
2171 \r
2172         //==============================================================================\r
2173         Activity activity;\r
2174         private long nativeContext = 0;\r
2175 \r
2176         private native void dispatchFinalize (long nativeContextRef);\r
2177         private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);\r
2178     }\r
2179 \r
2180     public InvocationHandler createInvocationHandler (long nativeContextRef)\r
2181     {\r
2182         return new NativeInvocationHandler (this, nativeContextRef);\r
2183     }\r
2184 \r
2185     public void invocationHandlerContextDeleted (InvocationHandler handler)\r
2186     {\r
2187         ((NativeInvocationHandler) handler).nativeContextDeleted();\r
2188     }\r
2189 \r
2190     //==============================================================================\r
2191     public static class HTTPStream\r
2192     {\r
2193         public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse,\r
2194                            String headersToUse, int timeOutMsToUse,\r
2195                            int[] statusCodeToUse, StringBuffer responseHeadersToUse,\r
2196                            int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException\r
2197         {\r
2198             isPost = isPostToUse;\r
2199             postData = postDataToUse;\r
2200             headers = headersToUse;\r
2201             timeOutMs = timeOutMsToUse;\r
2202             statusCode = statusCodeToUse;\r
2203             responseHeaders = responseHeadersToUse;\r
2204             totalLength = -1;\r
2205             numRedirectsToFollow = numRedirectsToFollowToUse;\r
2206             httpRequestCmd = httpRequestCmdToUse;\r
2207 \r
2208             connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);\r
2209         }\r
2210 \r
2211         private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,\r
2212                                                           String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException\r
2213         {\r
2214             HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());\r
2215 \r
2216             try\r
2217             {\r
2218                 newConnection.setInstanceFollowRedirects (false);\r
2219                 newConnection.setConnectTimeout (timeOutMs);\r
2220                 newConnection.setReadTimeout (timeOutMs);\r
2221 \r
2222                 // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines.\r
2223                 // So convert headers string to an array, with an element for each line\r
2224                 String headerLines[] = headers.split("\\n");\r
2225 \r
2226                 // Set request headers\r
2227                 for (int i = 0; i < headerLines.length; ++i)\r
2228                 {\r
2229                     int pos = headerLines[i].indexOf (":");\r
2230 \r
2231                     if (pos > 0 && pos < headerLines[i].length())\r
2232                     {\r
2233                         String field = headerLines[i].substring (0, pos);\r
2234                         String value = headerLines[i].substring (pos + 1);\r
2235 \r
2236                         if (value.length() > 0)\r
2237                             newConnection.setRequestProperty (field, value);\r
2238                     }\r
2239                 }\r
2240 \r
2241                 newConnection.setRequestMethod (httpRequestCmd);\r
2242 \r
2243                 if (isPost)\r
2244                 {\r
2245                     newConnection.setDoOutput (true);\r
2246 \r
2247                     if (postData != null)\r
2248                     {\r
2249                         OutputStream out = newConnection.getOutputStream();\r
2250                         out.write(postData);\r
2251                         out.flush();\r
2252                     }\r
2253                 }\r
2254 \r
2255                 return newConnection;\r
2256             }\r
2257             catch (Throwable e)\r
2258             {\r
2259                 newConnection.disconnect();\r
2260                 throw new IOException ("Connection error");\r
2261             }\r
2262         }\r
2263 \r
2264         private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException\r
2265         {\r
2266             synchronized (createFutureLock)\r
2267             {\r
2268                 if (hasBeenCancelled.get())\r
2269                     return null;\r
2270 \r
2271                 streamFuture = executor.submit (new Callable<BufferedInputStream>()\r
2272                 {\r
2273                     @Override\r
2274                     public BufferedInputStream call() throws IOException\r
2275                     {\r
2276                         return new BufferedInputStream (isInput ? connection.getInputStream()\r
2277                                                                 : connection.getErrorStream());\r
2278                     }\r
2279                 });\r
2280             }\r
2281 \r
2282             try\r
2283             {\r
2284                 return streamFuture.get();\r
2285             }\r
2286             catch (InterruptedException e)\r
2287             {\r
2288                 return null;\r
2289             }\r
2290             catch (CancellationException e)\r
2291             {\r
2292                 return null;\r
2293             }\r
2294         }\r
2295 \r
2296         public final boolean connect()\r
2297         {\r
2298             boolean result = false;\r
2299             int numFollowedRedirects = 0;\r
2300 \r
2301             while (true)\r
2302             {\r
2303                 result = doConnect();\r
2304 \r
2305                 if (! result)\r
2306                     return false;\r
2307 \r
2308                 if (++numFollowedRedirects > numRedirectsToFollow)\r
2309                     break;\r
2310 \r
2311                 int status = statusCode[0];\r
2312 \r
2313                 if (status == 301 || status == 302 || status == 303 || status == 307)\r
2314                 {\r
2315                     // Assumes only one occurrence of "Location"\r
2316                     int pos1 = responseHeaders.indexOf ("Location:") + 10;\r
2317                     int pos2 = responseHeaders.indexOf ("\n", pos1);\r
2318 \r
2319                     if (pos2 > pos1)\r
2320                     {\r
2321                         String currentLocation = connection.getURL().toString();\r
2322                         String newLocation = responseHeaders.substring (pos1, pos2);\r
2323 \r
2324                         try\r
2325                         {\r
2326                             // Handle newLocation whether it's absolute or relative\r
2327                             URL baseUrl = new URL (currentLocation);\r
2328                             URL newUrl  = new URL (baseUrl, newLocation);\r
2329                             String transformedNewLocation = newUrl.toString();\r
2330 \r
2331                             if (transformedNewLocation != currentLocation)\r
2332                             {\r
2333                                 // Clear responseHeaders before next iteration\r
2334                                 responseHeaders.delete (0, responseHeaders.length());\r
2335 \r
2336                                 synchronized (createStreamLock)\r
2337                                 {\r
2338                                     if (hasBeenCancelled.get())\r
2339                                         return false;\r
2340 \r
2341                                     connection.disconnect();\r
2342 \r
2343                                     try\r
2344                                     {\r
2345                                         connection = createConnection (transformedNewLocation, isPost,\r
2346                                                                        postData, headers, timeOutMs,\r
2347                                                                        httpRequestCmd);\r
2348                                     }\r
2349                                     catch (Throwable e)\r
2350                                     {\r
2351                                         return false;\r
2352                                     }\r
2353                                 }\r
2354                             }\r
2355                             else\r
2356                             {\r
2357                                 break;\r
2358                             }\r
2359                         }\r
2360                         catch (Throwable e)\r
2361                         {\r
2362                             return false;\r
2363                         }\r
2364                     }\r
2365                     else\r
2366                     {\r
2367                         break;\r
2368                     }\r
2369                 }\r
2370                 else\r
2371                 {\r
2372                     break;\r
2373                 }\r
2374             }\r
2375 \r
2376             return result;\r
2377         }\r
2378 \r
2379         private final boolean doConnect()\r
2380         {\r
2381             synchronized (createStreamLock)\r
2382             {\r
2383                 if (hasBeenCancelled.get())\r
2384                     return false;\r
2385 \r
2386                 try\r
2387                 {\r
2388                     try\r
2389                     {\r
2390                         inputStream = getCancellableStream (true);\r
2391                     }\r
2392                     catch (ExecutionException e)\r
2393                     {\r
2394                         if (connection.getResponseCode() < 400)\r
2395                         {\r
2396                             statusCode[0] = connection.getResponseCode();\r
2397                             connection.disconnect();\r
2398                             return false;\r
2399                         }\r
2400                     }\r
2401                     finally\r
2402                     {\r
2403                         statusCode[0] = connection.getResponseCode();\r
2404                     }\r
2405 \r
2406                     try\r
2407                     {\r
2408                         if (statusCode[0] >= 400)\r
2409                             inputStream = getCancellableStream (false);\r
2410                         else\r
2411                             inputStream = getCancellableStream (true);\r
2412                     }\r
2413                     catch (ExecutionException e)\r
2414                     {}\r
2415 \r
2416                     for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())\r
2417                     {\r
2418                         if (entry.getKey() != null && entry.getValue() != null)\r
2419                         {\r
2420                             responseHeaders.append(entry.getKey() + ": "\r
2421                                                    + android.text.TextUtils.join(",", entry.getValue()) + "\n");\r
2422 \r
2423                             if (entry.getKey().compareTo ("Content-Length") == 0)\r
2424                                 totalLength = Integer.decode (entry.getValue().get (0));\r
2425                         }\r
2426                     }\r
2427 \r
2428                     return true;\r
2429                 }\r
2430                 catch (IOException e)\r
2431                 {\r
2432                     return false;\r
2433                 }\r
2434             }\r
2435         }\r
2436 \r
2437         static class DisconnectionRunnable implements Runnable\r
2438         {\r
2439             public DisconnectionRunnable (HttpURLConnection theConnection,\r
2440                                           InputStream theInputStream,\r
2441                                           ReentrantLock theCreateStreamLock,\r
2442                                           Object theCreateFutureLock,\r
2443                                           Future<BufferedInputStream> theStreamFuture)\r
2444             {\r
2445                 connectionToDisconnect = theConnection;\r
2446                 inputStream = theInputStream;\r
2447                 createStreamLock = theCreateStreamLock;\r
2448                 createFutureLock = theCreateFutureLock;\r
2449                 streamFuture = theStreamFuture;\r
2450             }\r
2451 \r
2452             public void run()\r
2453             {\r
2454                 try\r
2455                 {\r
2456                     if (! createStreamLock.tryLock())\r
2457                     {\r
2458                         synchronized (createFutureLock)\r
2459                         {\r
2460                             if (streamFuture != null)\r
2461                                 streamFuture.cancel (true);\r
2462                         }\r
2463 \r
2464                         createStreamLock.lock();\r
2465                     }\r
2466 \r
2467                     if (connectionToDisconnect != null)\r
2468                         connectionToDisconnect.disconnect();\r
2469 \r
2470                     if (inputStream != null)\r
2471                         inputStream.close();\r
2472                 }\r
2473                 catch (IOException e)\r
2474                 {}\r
2475                 finally\r
2476                 {\r
2477                     createStreamLock.unlock();\r
2478                 }\r
2479             }\r
2480 \r
2481             private HttpURLConnection connectionToDisconnect;\r
2482             private InputStream inputStream;\r
2483             private ReentrantLock createStreamLock;\r
2484             private Object createFutureLock;\r
2485             Future<BufferedInputStream> streamFuture;\r
2486         }\r
2487 \r
2488         public final void release()\r
2489         {\r
2490             DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,\r
2491                                                                                      inputStream,\r
2492                                                                                      createStreamLock,\r
2493                                                                                      createFutureLock,\r
2494                                                                                      streamFuture);\r
2495 \r
2496             synchronized (createStreamLock)\r
2497             {\r
2498                 hasBeenCancelled.set (true);\r
2499 \r
2500                 connection = null;\r
2501             }\r
2502 \r
2503             Thread disconnectionThread = new Thread(disconnectionRunnable);\r
2504             disconnectionThread.start();\r
2505         }\r
2506 \r
2507         public final int read (byte[] buffer, int numBytes)\r
2508         {\r
2509             int num = 0;\r
2510 \r
2511             try\r
2512             {\r
2513                 synchronized (createStreamLock)\r
2514                 {\r
2515                     if (inputStream != null)\r
2516                         num = inputStream.read (buffer, 0, numBytes);\r
2517                 }\r
2518             }\r
2519             catch (IOException e)\r
2520             {}\r
2521 \r
2522             if (num > 0)\r
2523                 position += num;\r
2524 \r
2525             return num;\r
2526         }\r
2527 \r
2528         public final long getPosition()                 { return position; }\r
2529         public final long getTotalLength()              { return totalLength; }\r
2530         public final boolean isExhausted()              { return false; }\r
2531         public final boolean setPosition (long newPos)  { return false; }\r
2532 \r
2533         private boolean isPost;\r
2534         private byte[] postData;\r
2535         private String headers;\r
2536         private int timeOutMs;\r
2537         String httpRequestCmd;\r
2538         private HttpURLConnection connection;\r
2539         private int[] statusCode;\r
2540         private StringBuffer responseHeaders;\r
2541         private int totalLength;\r
2542         private int numRedirectsToFollow;\r
2543         private InputStream inputStream;\r
2544         private long position;\r
2545         private final ReentrantLock createStreamLock = new ReentrantLock();\r
2546         private final Object createFutureLock = new Object();\r
2547         private AtomicBoolean hasBeenCancelled = new AtomicBoolean();\r
2548 \r
2549         private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());\r
2550         Future<BufferedInputStream> streamFuture;\r
2551     }\r
2552 \r
2553     public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,\r
2554                                                      String headers, int timeOutMs, int[] statusCode,\r
2555                                                      StringBuffer responseHeaders, int numRedirectsToFollow,\r
2556                                                      String httpRequestCmd)\r
2557     {\r
2558         // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)\r
2559         if (timeOutMs < 0)\r
2560             timeOutMs = 0;\r
2561         else if (timeOutMs == 0)\r
2562             timeOutMs = 30000;\r
2563 \r
2564         for (;;)\r
2565         {\r
2566             try\r
2567             {\r
2568                 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,\r
2569                                                         timeOutMs, statusCode, responseHeaders,\r
2570                                                         numRedirectsToFollow, httpRequestCmd);\r
2571 \r
2572                 return httpStream;\r
2573             }\r
2574             catch (Throwable e) {}\r
2575 \r
2576             return null;\r
2577         }\r
2578     }\r
2579 \r
2580     public final void launchURL (String url)\r
2581     {\r
2582         startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));\r
2583     }\r
2584 \r
2585     private native boolean webViewPageLoadStarted (long host, WebView view, String url);\r
2586     private native void webViewPageLoadFinished (long host, WebView view, String url);\r
2587     private native void webViewReceivedError (long host, WebView view, WebResourceRequest request, WebResourceError error);    private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse);    private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);\r
2588     private native void webViewCloseWindowRequest (long host, WebView view);\r
2589     private native void webViewCreateWindowRequest (long host, WebView view);\r
2590 \r
2591     //==============================================================================\r
2592     public class JuceWebViewClient   extends WebViewClient\r
2593     {\r
2594         public JuceWebViewClient (long hostToUse)\r
2595         {\r
2596             host = hostToUse;\r
2597         }\r
2598 \r
2599         public void hostDeleted()\r
2600         {\r
2601             synchronized (hostLock)\r
2602             {\r
2603                 host = 0;\r
2604             }\r
2605         }\r
2606 \r
2607         @Override\r
2608         public void onPageFinished (WebView view, String url)\r
2609         {\r
2610             if (host == 0)\r
2611                 return;\r
2612 \r
2613             webViewPageLoadFinished (host, view, url);\r
2614         }\r
2615 \r
2616         @Override\r
2617         public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)\r
2618         {\r
2619             if (host == 0)\r
2620                 return;\r
2621 \r
2622             webViewReceivedSslError (host, view, handler, error);\r
2623         }\r
2624 \r
2625         @Override\r
2626         public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error)\r
2627         {\r
2628             if (host == 0)\r
2629                 return;\r
2630 \r
2631             webViewReceivedError (host, view, request, error);\r
2632         }\r
2633 \r
2634         @Override\r
2635         public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse)\r
2636         {\r
2637             if (host == 0)\r
2638                 return;\r
2639 \r
2640             webViewReceivedHttpError (host, view, request, errorResponse);\r
2641         }\r
2642 \r
2643         @Override\r
2644         public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)\r
2645         {\r
2646             synchronized (hostLock)\r
2647             {\r
2648                 if (host != 0)\r
2649                 {\r
2650                     boolean shouldLoad = webViewPageLoadStarted (host, view, request.getUrl().toString());\r
2651 \r
2652                     if (shouldLoad)\r
2653                         return null;\r
2654                 }\r
2655             }\r
2656 \r
2657             return new WebResourceResponse ("text/html", null, null);\r
2658         }\r
2659 \r
2660         private long host;\r
2661         private final Object hostLock = new Object();\r
2662     }\r
2663 \r
2664     public class JuceWebChromeClient    extends WebChromeClient\r
2665     {\r
2666         public JuceWebChromeClient (long hostToUse)\r
2667         {\r
2668             host = hostToUse;\r
2669         }\r
2670 \r
2671         @Override\r
2672         public void onCloseWindow (WebView window)\r
2673         {\r
2674             webViewCloseWindowRequest (host, window);\r
2675         }\r
2676 \r
2677         @Override\r
2678         public boolean onCreateWindow (WebView view, boolean isDialog,\r
2679                                        boolean isUserGesture, Message resultMsg)\r
2680         {\r
2681             webViewCreateWindowRequest (host, view);\r
2682             return false;\r
2683         }\r
2684 \r
2685         private long host;\r
2686         private final Object hostLock = new Object();\r
2687     }\r
2688 \r
2689 \r
2690     //==============================================================================\r
2691     public class CameraDeviceStateCallback  extends CameraDevice.StateCallback\r
2692     {\r
2693         private native void cameraDeviceStateClosed       (long host, CameraDevice camera);\r
2694         private native void cameraDeviceStateDisconnected (long host, CameraDevice camera);\r
2695         private native void cameraDeviceStateError        (long host, CameraDevice camera, int error);\r
2696         private native void cameraDeviceStateOpened       (long host, CameraDevice camera);\r
2697 \r
2698         CameraDeviceStateCallback (long hostToUse)\r
2699         {\r
2700             host = hostToUse;\r
2701         }\r
2702 \r
2703         @Override\r
2704         public void onClosed (CameraDevice camera)\r
2705         {\r
2706             cameraDeviceStateClosed (host, camera);\r
2707         }\r
2708 \r
2709         @Override\r
2710         public void onDisconnected (CameraDevice camera)\r
2711         {\r
2712             cameraDeviceStateDisconnected (host, camera);\r
2713         }\r
2714 \r
2715         @Override\r
2716         public void onError (CameraDevice camera, int error)\r
2717         {\r
2718             cameraDeviceStateError (host, camera, error);\r
2719         }\r
2720 \r
2721         @Override\r
2722         public void onOpened (CameraDevice camera)\r
2723         {\r
2724             cameraDeviceStateOpened (host, camera);\r
2725         }\r
2726 \r
2727         private long host;\r
2728     }\r
2729 \r
2730     //==============================================================================\r
2731     public class CameraCaptureSessionStateCallback  extends CameraCaptureSession.StateCallback\r
2732     {\r
2733         private native void cameraCaptureSessionActive          (long host, CameraCaptureSession session);\r
2734         private native void cameraCaptureSessionClosed          (long host, CameraCaptureSession session);\r
2735         private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session);\r
2736         private native void cameraCaptureSessionConfigured      (long host, CameraCaptureSession session);\r
2737         private native void cameraCaptureSessionReady           (long host, CameraCaptureSession session);\r
2738 \r
2739         CameraCaptureSessionStateCallback (long hostToUse)\r
2740         {\r
2741             host = hostToUse;\r
2742         }\r
2743 \r
2744         @Override\r
2745         public void onActive (CameraCaptureSession session)\r
2746         {\r
2747             cameraCaptureSessionActive (host, session);\r
2748         }\r
2749 \r
2750         @Override\r
2751         public void onClosed (CameraCaptureSession session)\r
2752         {\r
2753             cameraCaptureSessionClosed (host, session);\r
2754         }\r
2755 \r
2756         @Override\r
2757         public void onConfigureFailed (CameraCaptureSession session)\r
2758         {\r
2759             cameraCaptureSessionConfigureFailed (host, session);\r
2760         }\r
2761 \r
2762         @Override\r
2763         public void onConfigured (CameraCaptureSession session)\r
2764         {\r
2765             cameraCaptureSessionConfigured (host, session);\r
2766         }\r
2767 \r
2768         @Override\r
2769         public void onReady (CameraCaptureSession session)\r
2770         {\r
2771             cameraCaptureSessionReady (host, session);\r
2772         }\r
2773 \r
2774         private long host;\r
2775     }\r
2776 \r
2777     //==============================================================================\r
2778     public class CameraCaptureSessionCaptureCallback    extends CameraCaptureSession.CaptureCallback\r
2779     {\r
2780         private native void cameraCaptureSessionCaptureCompleted  (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result);\r
2781         private native void cameraCaptureSessionCaptureFailed     (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure);\r
2782         private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult);\r
2783         private native void cameraCaptureSessionCaptureStarted    (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber);\r
2784         private native void cameraCaptureSessionCaptureSequenceAborted   (long host, boolean isPreview, CameraCaptureSession session, int sequenceId);\r
2785         private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber);\r
2786 \r
2787         CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview)\r
2788         {\r
2789             host = hostToUse;\r
2790             preview = shouldBePreview;\r
2791         }\r
2792 \r
2793         @Override\r
2794         public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request,\r
2795                                         TotalCaptureResult result)\r
2796         {\r
2797             cameraCaptureSessionCaptureCompleted (host, preview, session, request, result);\r
2798         }\r
2799 \r
2800         @Override\r
2801         public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)\r
2802         {\r
2803             cameraCaptureSessionCaptureFailed (host, preview, session, request, failure);\r
2804         }\r
2805 \r
2806         @Override\r
2807         public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request,\r
2808                                          CaptureResult partialResult)\r
2809         {\r
2810             cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult);\r
2811         }\r
2812 \r
2813         @Override\r
2814         public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId)\r
2815         {\r
2816             cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId);\r
2817         }\r
2818 \r
2819         @Override\r
2820         public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber)\r
2821         {\r
2822             cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber);\r
2823         }\r
2824 \r
2825         @Override\r
2826         public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp,\r
2827                                       long frameNumber)\r
2828         {\r
2829             cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber);\r
2830         }\r
2831 \r
2832         private long host;\r
2833         private boolean preview;\r
2834     }\r
2835 \r
2836     //==============================================================================\r
2837     public class JuceOrientationEventListener    extends OrientationEventListener\r
2838     {\r
2839         private native void deviceOrientationChanged (long host, int orientation);\r
2840 \r
2841         public JuceOrientationEventListener (long hostToUse, Context context, int rate)\r
2842         {\r
2843             super (context, rate);\r
2844 \r
2845             host = hostToUse;\r
2846         }\r
2847 \r
2848         @Override\r
2849         public void onOrientationChanged (int orientation)\r
2850         {\r
2851             deviceOrientationChanged (host, orientation);\r
2852         }\r
2853 \r
2854         private long host;\r
2855     }\r
2856 \r
2857 \r
2858     //==============================================================================\r
2859     public class MediaControllerCallback  extends MediaController.Callback\r
2860     {\r
2861         private native void mediaControllerAudioInfoChanged     (long host, MediaController.PlaybackInfo info);\r
2862         private native void mediaControllerMetadataChanged      (long host, MediaMetadata metadata);\r
2863         private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);\r
2864         private native void mediaControllerSessionDestroyed     (long host);\r
2865 \r
2866         MediaControllerCallback (long hostToUse)\r
2867         {\r
2868             host = hostToUse;\r
2869         }\r
2870 \r
2871         @Override\r
2872         public void onAudioInfoChanged (MediaController.PlaybackInfo info)\r
2873         {\r
2874             mediaControllerAudioInfoChanged (host, info);\r
2875         }\r
2876 \r
2877         @Override\r
2878         public void onMetadataChanged (MediaMetadata metadata)\r
2879         {\r
2880             mediaControllerMetadataChanged (host, metadata);\r
2881         }\r
2882 \r
2883         @Override\r
2884         public void onPlaybackStateChanged (PlaybackState state)\r
2885         {\r
2886              mediaControllerPlaybackStateChanged (host, state);\r
2887         }\r
2888 \r
2889         @Override\r
2890         public void onQueueChanged (List<MediaSession.QueueItem> queue) {}\r
2891 \r
2892         @Override\r
2893         public void onSessionDestroyed()\r
2894         {\r
2895             mediaControllerSessionDestroyed (host);\r
2896         }\r
2897 \r
2898         private long host;\r
2899     }\r
2900 \r
2901     //==============================================================================\r
2902     public class MediaSessionCallback  extends MediaSession.Callback\r
2903     {\r
2904         private native void mediaSessionPause           (long host);\r
2905         private native void mediaSessionPlay            (long host);\r
2906         private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);\r
2907         private native void mediaSessionSeekTo          (long host, long pos);\r
2908         private native void mediaSessionStop            (long host);\r
2909 \r
2910 \r
2911         MediaSessionCallback (long hostToUse)\r
2912         {\r
2913             host = hostToUse;\r
2914         }\r
2915 \r
2916         @Override\r
2917         public void onPause()\r
2918         {\r
2919             mediaSessionPause (host);\r
2920         }\r
2921 \r
2922         @Override\r
2923         public void onPlay()\r
2924         {\r
2925             mediaSessionPlay (host);\r
2926         }\r
2927 \r
2928         @Override\r
2929         public void onPlayFromMediaId (String mediaId, Bundle extras)\r
2930         {\r
2931             mediaSessionPlayFromMediaId (host, mediaId, extras);\r
2932         }\r
2933 \r
2934         @Override\r
2935         public void onSeekTo (long pos)\r
2936         {\r
2937             mediaSessionSeekTo (host, pos);\r
2938         }\r
2939 \r
2940         @Override\r
2941         public void onStop()\r
2942         {\r
2943             mediaSessionStop (host);\r
2944         }\r
2945 \r
2946         @Override\r
2947         public void onFastForward() {}\r
2948 \r
2949         @Override\r
2950         public boolean onMediaButtonEvent (Intent mediaButtonIntent)\r
2951         {\r
2952             return true;\r
2953         }\r
2954 \r
2955         @Override\r
2956         public void onRewind() {}\r
2957 \r
2958         @Override\r
2959         public void onSkipToNext() {}\r
2960 \r
2961         @Override\r
2962         public void onSkipToPrevious() {}\r
2963 \r
2964         @Override\r
2965         public void onSkipToQueueItem (long id) {}\r
2966 \r
2967         private long host;\r
2968     }\r
2969 \r
2970     //==============================================================================\r
2971     public class SystemVolumeObserver extends ContentObserver\r
2972     {\r
2973         private native void mediaSessionSystemVolumeChanged (long host);\r
2974 \r
2975         SystemVolumeObserver (Activity activityToUse, long hostToUse)\r
2976         {\r
2977             super (null);\r
2978 \r
2979             activity = activityToUse;\r
2980             host = hostToUse;\r
2981         }\r
2982 \r
2983         void setEnabled (boolean shouldBeEnabled)\r
2984         {\r
2985             if (shouldBeEnabled)\r
2986                 activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);\r
2987             else\r
2988                 activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);\r
2989         }\r
2990 \r
2991         @Override\r
2992         public void onChange (boolean selfChange, Uri uri)\r
2993         {\r
2994             if (uri.toString().startsWith ("content://settings/system/volume_music"))\r
2995                 mediaSessionSystemVolumeChanged (host);\r
2996         }\r
2997 \r
2998         private Activity activity;\r
2999         private long host;\r
3000     }\r
3001 \r
3002 \r
3003     //==============================================================================\r
3004     public static final String getLocaleValue (boolean isRegion)\r
3005     {\r
3006         java.util.Locale locale = java.util.Locale.getDefault();\r
3007 \r
3008         return isRegion ? locale.getCountry()\r
3009                         : locale.getLanguage();\r
3010     }\r
3011 \r
3012     private static final String getFileLocation (String type)\r
3013     {\r
3014         return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();\r
3015     }\r
3016 \r
3017     public static final String getDocumentsFolder()\r
3018     {\r
3019         if (getAndroidSDKVersion() >= 19)\r
3020             return getFileLocation ("Documents");\r
3021 \r
3022         return Environment.getDataDirectory().getAbsolutePath();\r
3023     }\r
3024 \r
3025     public static final String getPicturesFolder()   { return getFileLocation (Environment.DIRECTORY_PICTURES); }\r
3026     public static final String getMusicFolder()      { return getFileLocation (Environment.DIRECTORY_MUSIC); }\r
3027     public static final String getMoviesFolder()     { return getFileLocation (Environment.DIRECTORY_MOVIES); }\r
3028     public static final String getDownloadsFolder()  { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }\r
3029 \r
3030     //==============================================================================\r
3031     @Override\r
3032     protected void onActivityResult (int requestCode, int resultCode, Intent data)\r
3033     {\r
3034         appActivityResult (requestCode, resultCode, data);\r
3035     }\r
3036 \r
3037     @Override\r
3038     protected void onNewIntent (Intent intent)\r
3039     {\r
3040         super.onNewIntent(intent);\r
3041         setIntent(intent);\r
3042 \r
3043         appNewIntent (intent);\r
3044     }\r
3045 \r
3046     //==============================================================================\r
3047     public final Typeface getTypeFaceFromAsset (String assetName)\r
3048     {\r
3049         try\r
3050         {\r
3051             return Typeface.createFromAsset (this.getResources().getAssets(), assetName);\r
3052         }\r
3053         catch (Throwable e) {}\r
3054 \r
3055         return null;\r
3056     }\r
3057 \r
3058     final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();\r
3059 \r
3060     public static String bytesToHex (byte[] bytes)\r
3061     {\r
3062         char[] hexChars = new char[bytes.length * 2];\r
3063 \r
3064         for (int j = 0; j < bytes.length; ++j)\r
3065         {\r
3066             int v = bytes[j] & 0xff;\r
3067             hexChars[j * 2]     = hexArray[v >>> 4];\r
3068             hexChars[j * 2 + 1] = hexArray[v & 0x0f];\r
3069         }\r
3070 \r
3071         return new String (hexChars);\r
3072     }\r
3073 \r
3074     final private java.util.Map dataCache = new java.util.HashMap();\r
3075 \r
3076     synchronized private final File getDataCacheFile (byte[] data)\r
3077     {\r
3078         try\r
3079         {\r
3080             java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");\r
3081             digest.update (data);\r
3082 \r
3083             String key = bytesToHex (digest.digest());\r
3084 \r
3085             if (dataCache.containsKey (key))\r
3086                 return (File) dataCache.get (key);\r
3087 \r
3088             File f = new File (this.getCacheDir(), "bindata_" + key);\r
3089             f.delete();\r
3090             FileOutputStream os = new FileOutputStream (f);\r
3091             os.write (data, 0, data.length);\r
3092             dataCache.put (key, f);\r
3093             return f;\r
3094         }\r
3095         catch (Throwable e) {}\r
3096 \r
3097         return null;\r
3098     }\r
3099 \r
3100     private final void clearDataCache()\r
3101     {\r
3102         java.util.Iterator it = dataCache.values().iterator();\r
3103 \r
3104         while (it.hasNext())\r
3105         {\r
3106             File f = (File) it.next();\r
3107             f.delete();\r
3108         }\r
3109     }\r
3110 \r
3111     public final Typeface getTypeFaceFromByteArray (byte[] data)\r
3112     {\r
3113         try\r
3114         {\r
3115             File f = getDataCacheFile (data);\r
3116 \r
3117             if (f != null)\r
3118                 return Typeface.createFromFile (f);\r
3119         }\r
3120         catch (Exception e)\r
3121         {\r
3122             Log.e ("JUCE", e.toString());\r
3123         }\r
3124 \r
3125         return null;\r
3126     }\r
3127 \r
3128     public static final int getAndroidSDKVersion()\r
3129     {\r
3130         return android.os.Build.VERSION.SDK_INT;\r
3131     }\r
3132 \r
3133     public final String audioManagerGetProperty (String property)\r
3134     {\r
3135         Object obj = getSystemService (AUDIO_SERVICE);\r
3136         if (obj == null)\r
3137             return null;\r
3138 \r
3139         java.lang.reflect.Method method;\r
3140 \r
3141         try\r
3142         {\r
3143             method = obj.getClass().getMethod ("getProperty", String.class);\r
3144         }\r
3145         catch (SecurityException e)     { return null; }\r
3146         catch (NoSuchMethodException e) { return null; }\r
3147 \r
3148         if (method == null)\r
3149             return null;\r
3150 \r
3151         try\r
3152         {\r
3153             return (String) method.invoke (obj, property);\r
3154         }\r
3155         catch (java.lang.IllegalArgumentException e) {}\r
3156         catch (java.lang.IllegalAccessException e) {}\r
3157         catch (java.lang.reflect.InvocationTargetException e) {}\r
3158 \r
3159         return null;\r
3160     }\r
3161 \r
3162     public final boolean hasSystemFeature (String property)\r
3163     {\r
3164         return getPackageManager().hasSystemFeature (property);\r
3165     }\r
3166 }\r